package com.zeddy.android.TouchPoint;

/*
 * Filename: 	TouchPointActivity.java
 * Classname: 	TouchPointActivity
 * Author:		i.z.chirombe@wustl.edu
 * Description:	This class is an abridgment of Harris's voice base human/robot 
 * 				interaction - meaning human assisted map localization, text2speech,
 * 				as well as speech recognition(speech2text) abilities, by utilizing 
 * 				Android platform as an external computing resource. Unlike 
 * 				HarrisAndroidActivity class, this class does is not aware of any 
 * 				PC connectivity, nor it ever concerned. Its just be a server that accepts 
 * 				client connections over the bluetooth - take request, compute response,
 * 				and replay back.
 * 
 * 				Before you run this application, make sure your android device is already
 * 				paired with parent devices that is connected to the PC
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import com.zeddy.android.HarrisVoice.HarrisBlueTransport;
import com.zeddy.android.HarrisVoice.HarrisBlueTransport.BluetoothErrors;
import com.zeddy.android.HarrisVoice.HarrisMessage;
import com.zeddy.android.HarrisVoice.HarrisMessage.HarrisMessageTypes;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;

/*
 * -As discussed above, this class is supposed to be run on a tablet, to be used as 
 * a hand held microphone with speaker in order for the robot to interactive with humans
 * seemlessly. Assuming localization of a robot could fail, a request can be send to this
 * module, so a human can touch point the desired location on the map, and responds with
 * the (x,y) coordinates. 
 * 
 */

public class TouchPointActivity extends Activity implements TextToSpeech.OnInitListener {
	private boolean DEBUG = true;
	private boolean TABLET_LOCALIZATION = false;
	private String TAG = "TouchPoint";
	private View touchView = null;
	private HarrisBlueTransport harrisBlueTransport = null;
	private Handler handler = null;
	private Handler speechHandler = null;
	private TextView edtNotepad = null;
	private int logClounter = 0;
	private boolean logViewOn = true;
	
	private TextToSpeech textToSpeech = null;
	private SpeechRecognizer speechRecognizer = null;
	private boolean speechRecognizerPending = false;
	private boolean speechToText = true;
	private int speechRecognizerId = 1001;
	
	private boolean androidServerActive = false;
	private AndroidServer androidServer = null;
	private HarrisMessage PendingMessage = new HarrisMessage();
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        handler = new Handler();
        speechHandler = new Handler();
        
        touchView = (View)findViewById(R.id.imageView); 
        touchView.setOnTouchListener(myTouchListener);

        edtNotepad = (TextView)findViewById(R.id.show_app_data);
        edtNotepad.setEnabled(false); 
            
        // activate bluetooth transport - 
        harrisBlueTransport = new HarrisBlueTransport(handler, edtNotepad);
        if(harrisBlueTransport.transSetup()!= BluetoothErrors.SUCCESS) {
        	showPost("Error: bluetoothSetup() failed...No IPad support applicable..."+
        			"info: To enable 'tabletGridSupport', pair a tableta nd restart this application. ");
        	harrisBlueTransport = null;
        }
        showPost("info: tabletGridSupport is - ON"); 
        
        // Initialize text-to-speech. This is an asynchronous operation.
        textToSpeech = new TextToSpeech(this, this); 
        //kick starting SpeechRecognizing facility -  
    	showPost("Init SpeechRecognizing - OK"); 
    	speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this); 
        speechRecognizer.setRecognitionListener(new HarrisSpeechRecognizer());
        
        // setup voice recognition - here 
        // Check to see if a recognition activity is present
        PackageManager pm = getPackageManager();
        List<ResolveInfo> activities = pm.queryIntentActivities(
                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
        if (activities.size() == 0) {
     	   showPost("No Voice Recognizer Found");
     	  speechToText = false;
     	  finish();
        }
        showPost("Voice Recognizer - OK");
     
        //start android server -  
		androidServer = new AndroidServer();
		androidServer.start();      
    }
    // Create an anonymous implementation of OnClickListener
    private OnTouchListener myTouchListener = new OnTouchListener() {
		@Override
		public boolean onTouch(View v, MotionEvent event) {
	        float x = event.getX();
	        float y = event.getY();
			if(TABLET_LOCALIZATION) {
	        	PendingMessage.add_entry("0");
	        	PendingMessage.add_entry(""+x);
	        	PendingMessage.add_entry(""+y);
	        	harrisBlueTransport.send_msg(PendingMessage.str());
	        	//showPost("TouchPoint("+x+", "+y+") - ");
				TABLET_LOCALIZATION = false;
				speak("Thank you very much!");
			} else {
				if(logViewOn)edtNotepad.setVisibility(View.INVISIBLE);
				//else edtNotepad.setVisibility(View.VISIBLE);
				//logViewOn = logViewOn ? false : true;
				edtNotepad.setVisibility(View.VISIBLE);
				logViewOn = false;
			}
			edtNotepad.setText("");
			showPost("TouchPoint("+x+", "+y+") - ");
			return false;
		}
    };
    // Implements TextToSpeech.OnInitListener.
    public void onInit(int status) {
        // status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
        if (status == TextToSpeech.SUCCESS) {
            // Set preferred language to US English.
            // Note that a language may not be available, and the result will indicate this.
            textToSpeech.setLanguage(Locale.US);
        } else {
            // Initialization failed.
            showPost("Could not initialize TextToSpeech.");
        }  
    }
    public void onDestroy() {
    	// notifying partner we are exiting..........
        HarrisMessage harrisMsg = new HarrisMessage();
        harrisMsg.MsgType = HarrisMessageTypes.ANDROID_UNDEFINED;
        harrisMsg.add_entry("zeddy");
        harrisMsg.add_entry("TouchPoint exiting.....");
        harrisBlueTransport.send_msg(harrisMsg.str());
        
        textToSpeech.shutdown();
        speechRecognizer.cancel();
        speechRecognizer.destroy();
        handler = null;
        harrisBlueTransport.cancel();
        harrisBlueTransport = null;
  	   	super.onDestroy();	      	
    }
    //----------
	public void sleep(final int seconds) { // generic sleep
		try {
			Thread.sleep(seconds);
		} catch (InterruptedException e) {
			if(DEBUG) Log.e(TAG, "Main::"+e);
		}
	}
	public void sleep2(String msg) { // sleep seconds proportional to the message 
		try {
			Thread.sleep(msg.length()*100);
		} catch (InterruptedException e) {
			if(DEBUG) Log.e(TAG, "Main::"+e);
		}
	}
	private void speak(String question) {
		try {
			textToSpeech.speak(question, TextToSpeech.QUEUE_FLUSH,null);
			//textToSpeech.speak(question, TextToSpeech.QUEUE_ADD,null);
		} catch (Exception e) { 
			if(DEBUG) Log.e(TAG, "Main::"+e);
		}
	}
	public void showPost(final String msg) {
		if(DEBUG) { Log.e(TAG,msg);}
		handler.post(new Runnable() {
			@Override
			public void run() {
				try {
					
					if(logClounter++ > 5) {
						edtNotepad.setText(msg);
						logClounter = 0;
					} else {
						edtNotepad.setText(msg+System.getProperty("line.separator")+edtNotepad.getText());
					}
				} catch (NullPointerException e) {
					if(DEBUG) Log.e(TAG, "showPost()::"+e);
				} catch (Exception e) {
					if(DEBUG) Log.e(TAG, "showPost()::"+e);
				}
			}	
		});
	}
	public void postReply(final String msg) {
		handler.post(new Runnable() {
			@Override
			public void run() {
				harrisBlueTransport.send_msg(msg);
			}	
		});
	}
  //----------
  	private class AndroidServer extends Thread {
  		private Handler serverHandler = new Handler();
  		public synchronized void run() {
  			HarrisMessage harrisMsg = new HarrisMessage();

          	BluetoothErrors transOp = harrisBlueTransport.listen();
          	if(transOp != BluetoothErrors.SUCCESS) {
          		showPost("Error: bt_listen() - ...exiting");
          		return;
          	}
          	
          	showPost("Client connection - ok....");
          	androidServerActive = true;
  			while(androidServerActive) {
  				// Keep listening until exception occurs or a socket is returned	
  				transOp = harrisBlueTransport.recv_msg(harrisMsg);
  				//in case we attempt to ready before the socket is ready - 
  				if(transOp == BluetoothErrors.ZERO_BYTE_READ) {
  					showPost("Zero Byte Read....try again");
  					continue;
  				}
  	        	//showPost("....trans-start....");
  	        	if((transOp != BluetoothErrors.SUCCESS) || 
  	        			(harrisMsg.MsgType == HarrisMessageTypes.ANDROID_UNDEFINED)){
  	        		showPost("Tranport Error : reseting Client Connection");
  	        		while(harrisBlueTransport.listen() != BluetoothErrors.SUCCESS) {
  	        			showPost("Still Reseting transport...");
  	        		}
  	        		showPost("Client connection - ok....");
  	        		continue;
  	        	}

  	        	
  	        	String myMsg = harrisMsg.str();
  	        	if(myMsg == "") {
  	        		continue;
  	        	}
  	        	
  	        	if(speechToText) {
  					showPost(harrisMsg.str());	
  					//Store this - so you can reconstruct this message from any callback - 
  					PendingMessage.reset();
  					PendingMessage.MsgType = harrisMsg.MsgType;
  					PendingMessage.MsgId = ++speechRecognizerId;
  					PendingMessage.add_entry(harrisMsg.entry(0));
  					PendingMessage.add_entry(harrisMsg.entry(1));
  					switch(harrisMsg.MsgType) {
  					case ANDROID_BROADCAST:		// one way voice message - 
  						speak(harrisMsg.entry(2)); // run text-to-speech to ask your question.
  						//showPost("\n....trans-stop....");
  						break;
  					case ANDROID_QUESTION: 		// Question human for help -  
  						speak(harrisMsg.entry(2)); // run text-to-speech to ask your question.
  						activateHarriesEars(harrisMsg.entry(2));
  						break;
  					case ANDROID_GRID_LOCATION:	// Ask human for a desired map location - 
  						speak(harrisMsg.entry(2)); // run text-to-speech to ask your question.
  						TABLET_LOCALIZATION = true;
  						break;
  					}
  				} else {
  					showPost("Error: No Speech Recognizer present...");
  				}
  			}		
  		}
  		public void activateHarriesEars(String question) {
  			//start voice recognition activity
  			serverHandler.postDelayed(new Runnable() { 
  				public void run() { 
  					showPost("...start-recording...");
  					beepEofSpeech();
  					if(!TABLET_LOCALIZATION) {
  						startVoiceRecording();
  					}
  				} 
  			}, question.length()*100);
  		}

        /** Will cancel the listening socket, and cause the thread to finish */
        @SuppressWarnings("unused")
  		
        public void cancel() {
        	close();
        }
        public void close() {
        	harrisBlueTransport.cancel();
        	harrisBlueTransport = null;
        }
  	}
  	//----------
	//-------------------------------------------------------------------
	public void beepEofSpeech () {
		Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
		MediaPlayer mMediaPlayer = new MediaPlayer();
		try {
			mMediaPlayer.setDataSource(this, soundUri);
			AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
			if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
				mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
				mMediaPlayer.setLooping(false);
				mMediaPlayer.prepare();
				mMediaPlayer.start();
			}
		} catch (SecurityException e) {
			showPost("pSound"+e);
		} catch (IllegalStateException e) {
			showPost("pSound"+e);
		} catch (IOException e) {
			showPost("pSound"+e);
		} catch (Exception e) {
			showPost("pSound"+e);
		}
		mMediaPlayer = null;
		soundUri = null;
	}
	/*
	 *  Voice Recognition starts here - 
     * Fire an intent to start the voice recognition activity.
     */
    private void startVoiceRecording() {
    	try {
        	Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);        
            intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
            intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,"Robot Voice recognition...");
            intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS,4); 
            speechRecognizer.startListening(intent); 
            //speechWatchdog(PendingMessage.MsgId);
            speechRecognizerPending = true;
    	} catch (ActivityNotFoundException e) {
    		showPost("VOICE: Nothing heard, ask agin...\n"+e);
    	}
    }
    @SuppressWarnings("unused")
	private void speechWatchdog(final int msg_id) {
		//start voice recognition activity
    	speechHandler.postDelayed(new Runnable() { 
			public void run() { 
				if((speechRecognizerPending) && (msg_id == PendingMessage.MsgId)) {
		        	//PendingMessage.add_entry("Speech Recognizer Timed out...");
		        	//harrisBlueTransport.send_msg(PendingMessage.str());
					speechRecognizer.stopListening();
					showPost("...timeout...stopping speech recording...");
					speechRecognizerPending = false;
				}
			} 
		}, 9000);
    }
    
    class HarrisSpeechRecognizer implements RecognitionListener          
    {
    	/* Seech Recognizer Error Codes - 
    		ERROR_NETWORK_TIMEOUT
    		ERROR_NETWORK
    		ERROR_AUDIO
    		ERROR_SERVER
    		ERROR_CLIENT
    		ERROR_SPEECH_TIMEOUT
    		ERROR_NO_MATCH
    		ERROR_RECOGNIZER_BUSY
    		ERROR_INSUFFICIENT_PERMISSIONS
    		*/
    	public HarrisSpeechRecognizer() {
    	}
    	public void onReadyForSpeech(Bundle params)
        {
        	//showPost("onReadyForSpeech");
        }
        public void onBeginningOfSpeech()
        {
        	//showPost("onBeginningOfSpeech");
        }
        public void onRmsChanged(float rmsdB)
        {
        	//showPost("onRmsChanged");
        }
        public void onBufferReceived(byte[] buffer)
        {
        	//showPost("onBufferReceived");
        }
        public void onEndOfSpeech()
        {
        	//showPost("onEndofSpeech");
        }
        public void onError(int error)
        {
            speechRecognizerPending = false;
        	String errorMsg = "error( "+error+" ) - ";
        	if(error == 2) {
        		errorMsg += "Android - without Internet, I am like fish with no water: Please activate G4 or wifi";
        	} else {
        		 errorMsg += "- speech recong problem";
        	}
        	PendingMessage.add_entry(""+error);
        	PendingMessage.add_entry(errorMsg);
        	harrisBlueTransport.send_msg(PendingMessage.str());
        	//postReply(PendingMessage.str());

            showPost(errorMsg);
        }
        public void onResults(Bundle results)                   
        {
        	speechRecognizerPending = false;
        	ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            String voiceMsg = "VOICE::("+matches.size()+")[ "+matches.get(0)+" ] ";
        	if(!TABLET_LOCALIZATION) {
                PendingMessage.add_entry("0");
            	PendingMessage.add_entry(matches.get(0));
            	harrisBlueTransport.send_msg(PendingMessage.str());
            	//postReply(PendingMessage.str());
            	
        	}
            showPost(voiceMsg);
        }
        public void onPartialResults(Bundle partialResults)
        {
        	//showPost("onPartialResults");
        }
        public void onEvent(int eventType, Bundle params)
        {
        	//showPost("onEvent " + eventType);
        }
    }
}